﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace GamingNetCode
{

    public class MessageType : Attribute
    {
        public String TypeCode
        {
            get;
            set;
        }

        public MessageType(string TypeCode)
        {
            this.TypeCode = TypeCode;
        }
    }

    public abstract class BaseTCPMessage
    {
        protected String mMessageType;

        public const String SIZEPLACEHOLDER = "000000000";
        public const int DATASTARTINDEX = 13;

        public String Sender
        {
            get;
            set;
        }

        public BaseTCPMessage() { }

        public BaseTCPMessage(String pSender)
        {
            this.Sender = pSender;
        }

        public virtual byte[] EncodeMessage()
        {
            PropertyInfo[] Info = this.GetType().GetProperties(BindingFlags.Public);

            Array.Sort(Info, delegate(PropertyInfo p1, PropertyInfo p2) { return p1.Name.CompareTo(p2.Name); });

            StringBuilder SB = new StringBuilder();

            SB.Append(GetMessageTypeCode(this.GetType()));
            SB.Append(CalculateLenth().ToString(SIZEPLACEHOLDER));
            SB.Append(this.Sender);
            foreach (PropertyInfo p in Info)
            {
                if (p.Name != "Sender")
                {
                    SB.Append("\t");
                    SB.Append(p.GetValue(this, null).ToString());
                }
            }

            return ASCIIEncoding.ASCII.GetBytes(SB.ToString());
        }

        public virtual void DecodeMessage(String Message)
        {
            string[] values = Message.Substring(DATASTARTINDEX).Split('\t');
            List<string> valuesList = new List<string>();
            valuesList.AddRange(values);

            PropertyInfo[] Info = this.GetType().GetProperties(BindingFlags.Public);

            Array.Sort(Info, delegate(PropertyInfo p1, PropertyInfo p2) { return p1.Name.CompareTo(p2.Name); });

            List<PropertyInfo> InfoList = new List<PropertyInfo>();
            InfoList.AddRange(Info);

            InfoList.Remove(InfoList.Find(delegate(PropertyInfo p) { return p.Name == "Sender"; }));

            this.Sender = valuesList[0];
            valuesList.RemoveAt(0);

            for (int i = 0; i < InfoList.Count; i++)
            {
                InfoList[i].SetValue(this, valuesList[i], null);
            }
            
        }

        public virtual int CalculateLenth()
        {
            PropertyInfo[] Info = this.GetType().GetProperties(BindingFlags.Public);

            Array.Sort(Info, delegate(PropertyInfo p1, PropertyInfo p2) { return p1.Name.CompareTo(p2.Name); });

            StringBuilder SB = new StringBuilder();

            SB.Append(GetMessageTypeCode(this.GetType()));
            SB.Append(SIZEPLACEHOLDER);
            SB.Append(this.Sender);
            foreach (PropertyInfo p in Info)
            {
                if (p.Name != "Sender")
                {
                    SB.Append("\t");
                    SB.Append(p.GetValue(this, null).ToString());
                }
            }

            return ASCIIEncoding.ASCII.GetByteCount(SB.ToString());
        }

        public static string GetMessageTypeCode(Type T)
        {
            MemberInfo Info = T;
            foreach (Attribute A in Info.GetCustomAttributes(false))
            {
                if (A.GetType() == typeof(MessageType))
                {
                    MessageType MT = (MessageType)A;
                    return MT.TypeCode;
                }
            }

            throw new Exception("Type Not Recognized");
        }

        public static Type GetMessageType(String S)
        {

            Type[] T = (from t in Assembly.GetExecutingAssembly().GetTypes() where t.BaseType == typeof(BaseTCPMessage) && t.GetConstructor(Type.EmptyTypes) != null select t).ToArray<Type>();

            foreach (Type t in T)
            {
                if (GetMessageTypeCode(t) == S)
                    return t;
            }

            throw new Exception("Type Code not recognized");
        }

        public static int GetMessageSize(String Message)
        {
            string sub = Message.Substring(4, SIZEPLACEHOLDER.Length);

            return int.Parse(sub);
        }

        public static int GetMessageSize(byte[] Message)
        {
            return GetMessageSize(ASCIIEncoding.ASCII.GetString(Message));
        }

        public static BaseTCPMessage GenerateMessage(string Message)
        {
            BaseTCPMessage temp =  (BaseTCPMessage)Activator.CreateInstance(GetMessageType(Message.Substring(0, 4)));

            temp.DecodeMessage(Message);

            return temp;
        }
    }
}
